#include "ByteBuffer.h"
#include "ByteConvert.h"
#include "util/Util.h"
using namespace yzrilyzr_lang;
using namespace yzrilyzr_util;
using namespace yzrilyzr_array;
namespace yzrilyzr_io{
	ByteBuffer::ByteBuffer(u_index mark, u_index pos, u_index lim, u_index cap){       // package-private
		if(cap < 0)
			throw createCapacityException(cap);
		this->_capacity=cap;
		limit(lim);
		position(pos);
		if(mark >= 0){
			if(mark > static_cast<u_index>(pos))
				throw IndexOutOfBoundsException("mark > position: (" + std::to_string(mark) + " > " + std::to_string(pos) + ")");
			this->_mark=mark;
		}
	}
	ByteBuffer::ByteBuffer(u_index mark, u_index pos, u_index lim, u_index cap, const ByteArray & hb, u_index offset) :ByteBuffer(mark, pos, lim, cap){
		this->hb=hb;
		this->_offset=offset;
	}
	Exception ByteBuffer::createCapacityException(u_index capacity){
		return Exception(String("capacity < 0: (") + std::to_string(capacity) + " < 0)");
	}
	Exception ByteBuffer::createPositionException(u_index newPosition){
		String msg;
		if(newPosition > _limit){
			msg=String("newPosition > limit: (") + std::to_string(newPosition) + " > " + std::to_string(_limit) + ")";
		} else{ // assume negative
			msg=String("newPosition < 0: (") + std::to_string(newPosition) + " < 0)";
		}

		return Exception(msg);
	}
	Exception ByteBuffer::createLimitException(u_index newLimit){
		String msg;
		if(newLimit > _capacity){
			msg=String("newLimit > capacity: (") + std::to_string(newLimit) + " > " + std::to_string(_capacity) + ")";
		} else{ // assume negative
			msg=String("newLimit < 0: (") + std::to_string(newLimit) + " < 0)";
		}
		return Exception(msg);
	}
	u_index ByteBuffer::nextGetIndex(){
		u_index p=_position;
		if(p >= _limit)
			throw IndexOutOfBoundsException();
		_position=p + 1;
		return p;
	}
	u_index ByteBuffer::nextGetIndex(u_index nb){
		u_index p=_position;
		if(_limit - p < nb)
			throw IndexOutOfBoundsException();
		_position=p + nb;
		return p;
	}
	u_index ByteBuffer::ix(u_index i){
		return i + _offset;
	}
	ByteBuffer & ByteBuffer::getArray(u_index index, const ByteArray & dst, u_index offset, u_index length){
		u_index end=offset + length;
		for(u_index i=offset, j=index; i < end; i++, j++){
			dst[i]=get(j);
		}
		return *this;
	}
	u_index ByteBuffer::checkIndex(u_index i, u_index nb){               // package-private
		if((i < 0) || (nb > static_cast<u_index>(_limit - i)))
			throw IndexOutOfBoundsException();
		return i;
	}
	u_index ByteBuffer::checkIndex(u_index i){
		Util::checkIndex(i, _limit);
		return i;
	}
	u_index ByteBuffer::byteOffset(u_index i){
		return i;
	}
	u_index ByteBuffer::nextPutIndex(){
		u_index p=_position;
		if(p >= static_cast<u_index>(_limit))
			throw Exception();
		_position=p + 1;
		return p;
	}
	u_index ByteBuffer::nextPutIndex(u_index nb){
		u_index p=_position;
		if(static_cast<u_index>(_limit) - p < nb)
			throw Exception();
		_position=p + nb;
		return p;
	}

	std::shared_ptr<ByteBuffer> ByteBuffer::wrap(const ByteArray & buf){
		return wrap(buf, 0, buf.length);
	}
	std::shared_ptr<ByteBuffer> ByteBuffer::wrap(const ByteArray & buf, u_index off, u_index len){
		return std::make_shared<ByteBuffer>(-1, off, off + len, buf.length, buf, 0);
	}
	std::shared_ptr<ByteBuffer> ByteBuffer::allocate(u_index len){
		ByteArray p(len);
		return wrap(p);
	}
	int8_t ByteBuffer::get(){
		return hb[ix(nextGetIndex())];
	}
	int8_t ByteBuffer::get(u_index i){
		return hb[ix(checkIndex(i))];
	}
	int16_t ByteBuffer::getShort(){
		return ByteConvert::getShort(hb, byteOffset(nextGetIndex(2)), bigEndian);
	}
	int16_t ByteBuffer::getShort(u_index index){
		return ByteConvert::getShort(hb, byteOffset(checkIndex(index, 2)), bigEndian);
	}
	int32_t ByteBuffer::getInt(){
		return ByteConvert::getInt(hb, byteOffset(nextGetIndex(4)), bigEndian);
	}
	int32_t ByteBuffer::getInt(u_index index){
		return ByteConvert::getInt(hb, byteOffset(checkIndex(index, 4)), bigEndian);
	}
	int64_t ByteBuffer::getLong(){
		return ByteConvert::getLong(hb, byteOffset(nextGetIndex(8)), bigEndian);
	}
	int64_t ByteBuffer::getLong(u_index index){
		return ByteConvert::getLong(hb, byteOffset(checkIndex(index, 8)), bigEndian);
	}
	ByteBuffer & ByteBuffer::put(int8_t x){
		hb[ix(nextPutIndex())]=x;
		return *this;
	}
	ByteBuffer & ByteBuffer::put(u_index i, int8_t x){
		hb[ix(checkIndex(i))]=x;
		return *this;
	}
	ByteBuffer & ByteBuffer::put(const ByteArray & x){
		put(x, 0, x.length);
		return *this;
	}
	ByteBuffer & ByteBuffer::put(const ByteArray & src, u_index offset, u_index length){
		u_index pos=position();
		if(length > limit() - pos)
			throw Exception();
		memcpy(hb._array + pos, src._array + offset, length);
		position(pos + length);
		return *this;
	}
	ByteBuffer & ByteBuffer::putShort(u_index i, int16_t x){
		ByteConvert::putShort(hb, byteOffset(checkIndex(i, 2)), x, bigEndian);
		return *this;
	}
	ByteBuffer & ByteBuffer::putShort(int16_t x){
		ByteConvert::putShort(hb, byteOffset(nextPutIndex(2)), x, bigEndian);
		return *this;
	}
	ByteBuffer & ByteBuffer::putInt(u_index i, int32_t x){
		ByteConvert::putInt(hb, byteOffset(checkIndex(i, 4)), x, bigEndian);
		return *this;
	}
	ByteBuffer & ByteBuffer::putInt(int32_t x){
		ByteConvert::putInt(hb, byteOffset(nextPutIndex(4)), x, bigEndian);
		return *this;
	}
	ByteBuffer & ByteBuffer::putLong(u_index i, int64_t x){
		ByteConvert::putLong(hb, byteOffset(checkIndex(i, 8)), x, bigEndian);
		return *this;
	}
	ByteBuffer & ByteBuffer::putLong(int64_t x){
		ByteConvert::putLong(hb, byteOffset(nextPutIndex(8)), x, bigEndian);
		return *this;
	}

	ByteBuffer & ByteBuffer::order(ByteOrder order){
		this->bigEndian=order == ByteOrder::BIG_ENDIAN;
		return *this;
	}
	u_index ByteBuffer::limit(){
		return _limit;
	}
	ByteBuffer & ByteBuffer::limit(u_index newLimit){
		if(newLimit > _capacity || newLimit < 0)
			throw createLimitException(newLimit);
		_limit=newLimit;
		if(_position > newLimit) _position=newLimit;
		if(static_cast<u_index>(_mark) > newLimit) _mark=-1;
		return *this;
	}
	u_index ByteBuffer::capacity()const{
		return _capacity;
	}
	u_index ByteBuffer::position()const{
		return _position;
	}
	ByteBuffer & ByteBuffer::position(u_index newPosition){
		if(newPosition > _limit || newPosition < 0)
			throw createPositionException(newPosition);
		if(static_cast<u_index>(_mark) > newPosition) _mark=-1;
		_position=newPosition;
		return *this;
	}
	ByteBuffer & ByteBuffer::get(const ByteArray & dst){
		return get(dst, 0, dst.length);
	}
	ByteBuffer & ByteBuffer::get(const ByteArray & dst, u_index offset, u_index length){
		u_index pos=position();
		if(length > limit() - pos)
			throw IndexOutOfBoundsException();
		getArray(pos, dst, offset, length);
		position(pos + length);
		return *this;
	}
	ByteArray ByteBuffer::array(){
		return hb;
	}
}